/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <baja/transition_tracking/transition_tracking_service.h>
#include <journal/models/page_model.h>
#include <journal/models/page_model_transitions.h>
#include <journal/models/ink_surface_model_factory.h>
#include <journal/models/photo_model_factory.h>

using namespace journal::model;

namespace bt = baja::transition_tracking;

const baja::uint32 page_model::_inkSurfaceCreatePass = 0;
const baja::uint32 page_model::_photosCreatePass = 1;
const baja::uint32 page_model::_photosLoadPass = 1;

page_model::page_model(
    const b::uint32& typeToken,
    const std::shared_ptr<inotebook_model>& parent,
    const std::shared_ptr<journal::model::db::ipage_table>& pageTable,
    const b::uint32& pageId
        ) : _currentLoadLevel(page_level::page_none),
            _lastRequestLoadLevel(page_level::page_none),
            _inDB(false),
            _initialized(false),
            _disposed(false),
            _intermediateState(none),
            _linkedData(false),
            _pagePaperType(0),
            _photos(),
            _parent(parent), 
            _pageTable(pageTable),
            _inkSurfaceId(0),
            _inkSurfaceIdReady(false),
            _photosIdsReady(false),
            _thumbnailUpToDate(false),
            _lastChangeTick(0),
            _thumbnailSnapshotTick(0),
            _savedThumbnailSnapshotTick(0)
{
    // generic_model
    setGenericIdentity(pageId);
    setGenericTypeToken(typeToken);
    setGenericParent(std::dynamic_pointer_cast<bm::igeneric_model>(parent));
    // tracked_object
    setTrackingService(constants::undoRedoService());

    constants::memoryTrackingService()->logCreate(genericTypeToken(), genericIdentity(), sizeof(page_model));
}

page_model::~page_model()
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L"XX page_model::~page_model() : genericIdentity() = %i") %
        genericIdentity()
        );

    constants::memoryTrackingService()->logDispose(genericTypeToken(), genericIdentity(), sizeof(page_model));
}

std::shared_ptr<page_model> page_model::create(
    const b::uint32& typeToken,
    const std::shared_ptr<inotebook_model>& parent, 
    const std::shared_ptr<journal::model::db::ipage_table>& pageTable,
    const b::uint32& pageId)
{
    std::shared_ptr<page_model> returnValue = std::make_shared<page_model>(typeToken, parent, pageTable, pageId);

    returnValue->_inkSurfaceFactory = createInkSurfaceModelFactory(pageTable->common()->dbEngine());
    returnValue->_photoModelFactory = createPhotoModelFactory(pageTable->common()->dbEngine());
    return returnValue;
}

void page_model::observeInkSurface(
    const std::shared_ptr<iink_surface_model>& inkSurface)
{
    if (inkSurface == nullptr)
    {
        return;
    }
    _observedObjects.inkSurface.inkSurfaceCookie = _inkSurface->addObserver(std::dynamic_pointer_cast<iink_surface_model_observer>(shared_from_this()));

    std::shared_ptr<bm::igeneric_model> genericModel = std::dynamic_pointer_cast<bm::igeneric_model>(_inkSurface);
    _observedObjects.inkSurface.genericInkSurfaceCookie = genericModel->addGenericObserver( std::dynamic_pointer_cast<bm::igeneric_model_observer>(shared_from_this()) );
}

void page_model::unobserveInkSurface(
    const std::shared_ptr<iink_surface_model>& inkSurface)
{
    if (inkSurface == nullptr)
    {
        return;
    }
    if (_observedObjects.inkSurface.inkSurfaceCookie != -1)
    {
        _inkSurface->removeObserver(_observedObjects.inkSurface.inkSurfaceCookie);
        _observedObjects.inkSurface.inkSurfaceCookie = -1;
    }

    if (_observedObjects.inkSurface.genericInkSurfaceCookie != -1)
    {
        std::shared_ptr<bm::igeneric_model> genericModel = std::dynamic_pointer_cast<bm::igeneric_model>(_inkSurface);
        genericModel->removeGenericObserver( _observedObjects.inkSurface.genericInkSurfaceCookie );
        _observedObjects.inkSurface.genericInkSurfaceCookie = -1;
    }
}

void page_model::observePhoto(
    const std::shared_ptr<iphoto_model>& photo)
{
    if (photo == nullptr)
    {
        return;
    }
    observed_photo observedPhoto;

    observedPhoto.photoCookie = photo->addObserver(std::dynamic_pointer_cast<iphoto_model_observer>(shared_from_this()));

    std::shared_ptr<bm::igeneric_model> genericModel = std::dynamic_pointer_cast<bm::igeneric_model>(photo);
    observedPhoto.genericPhotoCoockie = genericModel->addGenericObserver(std::dynamic_pointer_cast<bm::igeneric_model_observer>(shared_from_this()));

    _observedObjects.photos[photo->storageId()] = observedPhoto;
}

void page_model::unobservePhoto(
    const std::shared_ptr<iphoto_model>& photo)
{
    if (photo == nullptr)
    {
        return;
    }
    observed_photo observedPhoto = _observedObjects.photos[photo->storageId()];

    if (observedPhoto.photoCookie != -1)
    {
        photo->removeObserver(observedPhoto.photoCookie);
        observedPhoto.photoCookie = -1;
    }

    if (observedPhoto.genericPhotoCoockie != -1)
    {
        std::shared_ptr<bm::igeneric_model> genericModel = std::dynamic_pointer_cast<bm::igeneric_model>(photo);
        genericModel->removeGenericObserver(observedPhoto.genericPhotoCoockie);
        observedPhoto.genericPhotoCoockie = -1;
    }

    _observedObjects.photos.erase(photo->storageId());
}

b::Boolean page_model::isAccessible()
{
    return _initialized && !_disposed;
}

void page_model::initialize(const b::uint32& pagePaperType)
{
    _pagePaperType = pagePaperType;

    // ink surface
    _inkSurface = _inkSurfaceFactory->createInkSurface(shared_from_this());
    _inkSurfaceId = _inkSurface->storageId();

    observeInkSurface(_inkSurface);

    std::shared_ptr<iinternal_ink_surface_model> inkSurface = std::dynamic_pointer_cast<iinternal_ink_surface_model>(_inkSurface);
    inkSurface->initialize();

    _initialized = true;
    _currentLoadLevel = page_level::page_full;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> <<>> page_model::initialize() : id = %i : firing onPageInitialized(page = %i)") %
        genericIdentity() %
        genericIdentity()
        );

    _internalObserverSource.raise(
        [&](const std::shared_ptr<iinternal_page_model_observer>& observer, void* context)
        {
            observer->onPageInitialized(shared_from_this());
        });
}

std::shared_ptr<inotebook_model> page_model::parent()
{ 
    return _parent.lock();
}

std::shared_ptr<iphoto_model> page_model::createPhotoModel()
{
    std::shared_ptr<iphoto_model> photo;
        photo = _photoModelFactory->createPhoto(shared_from_this());

    observePhoto(photo);

    registerCreateChild(
        page_model_transition_type::createChildTransition,
        _photoModelFactory->typeToken(),
        photo->storageId());

    return photo;
}

std::shared_ptr<iphoto_model> page_model::recreatePhoto(
    const b::uint32& photoId)
{
    std::shared_ptr<iphoto_model> photo;
    photo = _photoModelFactory->recreatePhoto(shared_from_this(), photoId);

    observePhoto(photo);

    registerCreateChild(
        page_model_transition_type::createChildTransition,
        _photoModelFactory->typeToken(),
        photo->storageId());

    return photo;
}

void page_model::initializePhotoModelAsync(
    const std::shared_ptr<iphoto_model>& photo,
    const std::shared_ptr<b::storage::istorage_file>& originalFile,
    const b::float32& scale,
    const m::point2d& position,
    const b::float32& angle,
    const std::function<void()> initializeCompleted)
{
    std::dynamic_pointer_cast<iinternal_photo_model>(photo)->initializeAsync(
        originalFile,
        scale,
        position,
        angle,
        initializeCompleted
        );
}

void page_model::addPhoto(
    const std::shared_ptr<b::storage::istorage_file>& file,
    const b::float32& scale,
    const m::point2d& position,
    const b::float32& angle
    )
{
    if (!isAccessible())
    {
        return;
    }

    std::shared_ptr<bt::transition_context> tc = std::make_shared<bt::transition_context>(constants::undoRedoService());

    std::shared_ptr<iphoto_model> photo = createPhotoModel();

    {
        BAJA_LOCK(_mutex);
        _photoIds.push_back(photo->storageId());
    }

    initializePhotoModelAsync(photo, file, scale, position, angle,
        [this, tc, photo]()
        {
            this->attachPhotoModel(photo);

            this->onSelfChanged();

            // write to disk
            concurrency::task<void> t(
                [this, photo]()
                {
                    BAJA_LOCK(_mutex);

                    std::shared_ptr<iinternal_photo_model> internalPhoto = std::dynamic_pointer_cast<iinternal_photo_model>(photo);
                    internalPhoto->createInDB();
                    _pageTable->updatePhotoIds(genericIdentity(), _photoIds);
                });
        }
    );

}

void page_model::attachPhotoModel(
    const std::shared_ptr<iphoto_model>& photo)
{
    {
        BAJA_LOCK(_mutex);

    // attach
    _photos.push_back(photo);

    if (_linkedData == false)
    {
        _linkedData = true;
        _pageTable->updateLinkedData(genericIdentity(), _linkedData);
    }

    // Undo/Redo is not supported yet!
    // register attach
    //registerAttachChild(
    //    page_model_transition_type::attachChildTransition,
    //    _photoModelFactory->typeToken(),
    //    photo->storageId(),
    //    static_cast<b::uint32>(_photos.size() - 1));

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> <<>> page_model::attachPhotoModel() : id = %i : firing onPhotoModelAdded(photo = %i)") %
        genericIdentity() %
        photo->storageId()
        );
    }

    // announce attach
    _observerSource.raise(
        [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
        {
            observer->onPhotoModelAdded(photo);
        });
}

b::uint32 page_model::photoCount()
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return 0;
    }

    return static_cast<b::uint32>(_photos.size());
}

void page_model::detachPhotoModel(
        const id& id,
        std::shared_ptr<iphoto_model>& photoToRemove,
        b::uint32& photoToRemoveIndex)
{
    {
        BAJA_LOCK(_mutex);

    std::vector<std::shared_ptr<iphoto_model>> tempPhotos;
    b::uint32 photoIndex = 0;

    concurrency::concurrent_vector<std::shared_ptr<iphoto_model>>::iterator photoIt;
    for (photoIt = _photos.begin(); photoIt < _photos.end(); ++photoIt)
    {
        if ( (*photoIt)->storageId() == id )
        {
            photoToRemove = *photoIt;
            photoToRemoveIndex = photoIndex;
        }
        else
        {
            tempPhotos.push_back(*photoIt);
        }
        ++photoIndex;
    }

    if (photoToRemove != nullptr)
    {
        registerDetachChild(
            page_model_transition_type::detachChildTransition,
            _photoModelFactory->typeToken(),
            photoToRemove->storageId(),
            photoToRemoveIndex);

        // rebuild the vector...
        _photos.clear();
        _photoIds.clear();
        
        std::vector<std::shared_ptr<iphoto_model>>::iterator photoIt;
        for (photoIt = tempPhotos.begin(); photoIt < tempPhotos.end(); ++photoIt)
        {
            _photos.push_back(*photoIt);
            _photoIds.push_back( (*photoIt)->storageId() );
        }
        _pageTable->updatePhotoIds(genericIdentity(), _photoIds);

        unobservePhoto(photoToRemove);
        }
    }

    if (photoToRemove)
    {
        // raise photo removed event
        _observerSource.raise(
            [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
            {
                observer->onPhotoModelRemoved(photoToRemove);
            });
    }
}

void page_model::removePhoto(const id& id)
{
    if (!isAccessible())
    {
        return;
    }

    bt::transition_context tc(constants::undoRedoService());

    b::uint32 photoToRemoveIndex = 0;
    std::shared_ptr<iphoto_model> photoToRemove;

    detachPhotoModel(id, photoToRemove, photoToRemoveIndex);

    if (photoToRemove != nullptr)
    {
        // delete from disk...
        std::shared_ptr<iinternal_photo_model> photoInternal = std::dynamic_pointer_cast<iinternal_photo_model>(photoToRemove);
        photoInternal->activate(false);

        onSelfChanged();

        // Undo/Redo is not supported yet!
        //registerDeleteChild(
        //    page_model_transition_type::deleteChildTransition,
        //    _photoModelFactory->typeToken(),
        //    photoToRemove->storageId());

        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
            boost::wformat(L">> <<>> page_model::removePhoto() : page id = %i : photo count = %i : firing onPhotoModelRemoved(photo = %i)") %
            genericIdentity() %
            _photos.size() %
            photoToRemove->storageId()
            );
    }
}

void page_model::bringPhotoToFront(const id& id)
{
    std::shared_ptr<iphoto_model> photoToBringFront = nullptr;
    {
        BAJA_LOCK(_mutex);

        if (!isAccessible())
        {
            return;
        }

        /* Bring the specified photo to the front by the photo at the end of the list of photos.  The list is order by z-order.  
           The bottom most photo is at the begin of the list. */
        concurrency::concurrent_vector<std::shared_ptr<iphoto_model>> tempPhotos;
        std::vector<b::uint32> tempPhotoIds;
        concurrency::concurrent_vector<std::shared_ptr<iphoto_model>>::iterator photoIt;
        for (auto &photo : _photos)
        {
            if (photo->storageId() == id )
            {
                photoToBringFront = photo;
            }
            else
            {
                tempPhotos.push_back(photo);
                tempPhotoIds.push_back(photo->storageId());
            }
        }
        BAJA_ASSERT(photoToBringFront != nullptr, L"Attempting to change the Z-order of an non-existing photo.");
        tempPhotos.push_back(photoToBringFront);
        tempPhotoIds.push_back(photoToBringFront->storageId());
        _photos.swap(tempPhotos);
        _photoIds.swap(tempPhotoIds);
    
        _pageTable->updatePhotoIds(genericIdentity(), _photoIds);
    }

    _observerSource.raise(
        [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
    {
        observer->onPhotoModelBroughtToFront(photoToBringFront);
    });
}

b::uint32 page_model::pageNumber()
{
    // Pages don't know what their page number is -- they don't need to.
    // This should be removed eventually, as very very few data structures
    // really need to know what the page number is, and we should just rely
    // on page ID's or on the notebook if you need page number info.

    return static_cast<b::uint32>(_parent.lock()->getPageNumberFromPageId(genericIdentity()));
}

std::shared_ptr<iink_surface_model> page_model::inkSurface()
{
    if (!isAccessible())
    {
        return nullptr;
    }

    return _inkSurface;
}

b::int32 page_model::addObserver(const std::shared_ptr<ipage_model_observer>& observer)
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> <<>> page_model::addObserver() : id = %i : an observer has been added.") %
        genericIdentity()
        );

    return _observerSource += observer;
}

b::int32 page_model::addInternalObserver(const std::shared_ptr<iinternal_page_model_observer>& observer)
{
    BAJA_LOCK(_mutex);

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> <<>> page_model::addInternalObserver() : id = %i : an observer has been added.") %
        genericIdentity()
        );

    return _internalObserverSource += observer;
}

void page_model::removeInternalObserver(const b::int32 cookie)
{
    BAJA_LOCK(_mutex);

    _internalObserverSource -= cookie;
}

void page_model::removeObserver(const std::shared_ptr<ipage_model_observer>& observer)
{
    BAJA_LOCK(_mutex);

    _observerSource -= observer;
}

void page_model::removeObserver(const b::int32 cookie)
{
    _observerSource -= cookie;
}

b::uint32 page_model::storageId()
{
    return genericIdentity();
}

baja::uint32 page_model::paperType()
{
    return _pagePaperType;
}

void page_model::setPaperType(b::uint32 paperType)
{
    b::Boolean changed = false;

    {
        BAJA_LOCK(_mutex);

        if (!isAccessible())
        {
            return;
        }
        if (paperType != _pagePaperType)
        {
            _pagePaperType = paperType;
            _pageTable->updatePagePaperType(genericIdentity(), _pagePaperType);
            changed = true;
        }
    }

    if (changed)
    {
        onSelfChanged();

        _observerSource.raise(
            [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
            {
                observer->onPageModelPaperTypeChanged(paperType);
            });
    }
}

void page_model::removeFromDB()
{
    BAJA_LOCK(_mutex);
    if (!isAccessible() || (_inDB == false))
    {
        return;
    }

    DEBUG_MODEL_DUMP(this, false, L"<< page_model::removeFromDB()");

    _pageTable->dispose(genericIdentity());
    _inDB = false;
}

void page_model::createInDB()
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return;
    }

    b::stopwatch ws;

    if (_inDB == true)
    {
        return;
    }

    DEBUG_MODEL_DUMP(this, false, L"<< page_model::saveSelf()    :");

    _pageTable->insert(
        genericIdentity(),
        _linkedData,
        _thumbnailUpToDate,
        _pagePaperType);

    std::shared_ptr<iinternal_ink_surface_model> internalInkSurface = std::dynamic_pointer_cast<iinternal_ink_surface_model>(_inkSurface);
    internalInkSurface->createInDB();

    _inDB = true;

    constants::performanceService()->collect(performance_event::modelSave, ws.getElapsedMilliseconds()); 
}

b::Boolean page_model::hasLinkedData()
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return false;
    }

    return _linkedData;
}

b::Boolean page_model::isThumbnailUpToDate()
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return false;
    }

    return _thumbnailUpToDate;
}

void page_model::loadThumbnail()
{
    BAJA_LOCK(_mutex);

    b::stopwatch ws;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> loading thumbnail...      : genericIdentity() = %i") %
        genericIdentity()
        );

    _pageTable->loadThumbnail(genericIdentity(), _thumbnailUpToDate, _thumbnail);

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> loaded thumbnail          : genericIdentity() = %i : _thumbnail._size = %i : _thumbnail._blob[0] = %i : _thumbnail._blob[1] = %i") %
        genericIdentity() %
        _thumbnail._size %
        (_thumbnail._size >= 4 ? (static_cast<b::uint32*>(_thumbnail._buffer))[0] : 0) %
        (_thumbnail._size >= 8 ? (static_cast<b::uint32*>(_thumbnail._buffer))[1] : 0)
        );

    constants::performanceService()->collect(performance_event::modelLoad, ws.getElapsedMilliseconds()); 
}

void page_model::setThumbnailSnapshotPoint()
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return;
    }
    _thumbnailSnapshotTick = GetTickCount64();
}

void page_model::onChanged()
{
    if (_thumbnailUpToDate == true)
    {
        // we need to update its value only if it has changed.
        _thumbnailUpToDate = false;
        _pageTable->updateThumbnailStatus(genericIdentity(), _thumbnailUpToDate);
    }
    _lastChangeTick = GetTickCount64();

    fireChangedEvent();
}

void page_model::saveThumbnail(std::shared_ptr<s::binary_blob> blobToSave, b::Boolean thumbnailUpToDate)
{
    b::stopwatch ws;
    {
        BAJA_LOCK(_mutex);
        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
            boost::wformat(L"<< saving thumbnail          : genericIdentity() = %i : inBlob._size = %i : inBlob._blob[0] = %i : inBlob._blob[1] = %i") %
            genericIdentity() %
            blobToSave->_size %
            (blobToSave->_size ? (static_cast<b::uint32*>(blobToSave->_buffer))[0] : 0) %
            (blobToSave->_size ? (static_cast<b::uint32*>(blobToSave->_buffer))[1] : 0)
            );

        _pageTable->updateThumbnail(genericIdentity(), thumbnailUpToDate, *blobToSave);

        constants::performanceService()->collect(performance_event::modelSave, ws.getElapsedMilliseconds()); 
    }

    _observerSource.raise(
        [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
        {
            observer->onThumbnailSaved(shared_from_this());
        });
}

void page_model::saveThumbnailAsync(s::binary_blob& inBlob)
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L"<< scheduling thumbnail save : genericIdentity() = %i : _thumbnail._size = %i : _thumbnail._blob[0] = %i : _thumbnail._blob[1] = %i") %
        genericIdentity() %
        inBlob._size %
        (static_cast<b::uint32*>(inBlob._buffer))[0] %
        (static_cast<b::uint32*>(inBlob._buffer))[1]
        );

    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return;
    }

    // we take a copy because we need the inBlob to pass to the save function.
    _thumbnail.createAndCopy(inBlob._size, inBlob._buffer);
    _thumbnailUpToDate = _lastChangeTick <= _thumbnailSnapshotTick;

    // if a change occured after the last change...
    if (_lastChangeTick >= _savedThumbnailSnapshotTick)
    {
        // update the last save time...
        _savedThumbnailSnapshotTick = _thumbnailSnapshotTick;

        // save...
        std::shared_ptr<page_model> p_this = std::static_pointer_cast<page_model>(shared_from_this());
        std::shared_ptr<s::binary_blob> blobToSave = std::make_shared<s::binary_blob>(inBlob);
        b::Boolean thumbnailUpToDate = _thumbnailUpToDate;

        concurrency::task<void> t([p_this, blobToSave, thumbnailUpToDate]()
        {
            ACQUIRE_VIEW_LOCK;
            p_this->saveThumbnail(blobToSave, thumbnailUpToDate);
        });
    }
}

void page_model::thumbnail(s::binary_blob& outBlob)
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> giving out thumbnail : genericIdentity() = %i : _thumbnail._size = %i : _thumbnail._blob[0] = %i : _thumbnail._blob[1] = %i") %
        genericIdentity() %
        _thumbnail._size %
        ((_thumbnail._size > 0) ? (static_cast<b::uint32*>(_thumbnail._buffer))[0] : -1) %
        ((_thumbnail._size > 1) ? (static_cast<b::uint32*>(_thumbnail._buffer))[1] : -1)
        );

    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return;
    }

    outBlob.moveFrom(_thumbnail);
}

void page_model::loadSelf()
{
    BAJA_LOCK(_mutex);

    _pageTable->load(genericIdentity(), _linkedData, _pagePaperType, _thumbnailUpToDate, _photoIds);

    loadChildrenLinks();

    _initialized = true;
    _inDB = true;
    DEBUG_MODEL_DUMP(this, false, L">> page_model loaded         :");
}

void page_model::loadChildrenLinks()
{
    // ink surface...
    std::vector<b::uint32> inkSurfaceIds;
    _inkSurfaceFactory->getPersistedInkSurfaceIds(genericIdentity(), inkSurfaceIds);

    BAJA_ASSERT(inkSurfaceIds.size() == 1, "Page cannot be associated with more than one ink surface!");

    std::vector<b::uint32>::iterator idIt = inkSurfaceIds.begin();
    _inkSurfaceId = *idIt;
}

void page_model::recreateInkSurface()
{
    if (_inkSurface == nullptr)
    {
        _inkSurface = _inkSurfaceFactory->recreateInkSurface(shared_from_this(), _inkSurfaceId);
    }
    observeInkSurface(_inkSurface);
}

void page_model::loadInkSurface(b::uint32 pass, bool& done)
{
    std::dynamic_pointer_cast<iinternal_ink_surface_model>(_inkSurface)->load(pass, done);
}

void page_model::unloadInkSurface()
{
    std::dynamic_pointer_cast<iinternal_ink_surface_model>(_inkSurface)->unload();
}

void page_model::uncreateInkSurface()
{
    unobserveInkSurface(_inkSurface);
}

void page_model::unloadFull()
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> UNloading page...      : genericIdentity() = %i : full -> self...") %
        genericIdentity()
        );

    if (_currentLoadLevel != page_level::page_full)
    {
        return;
    }

    if (_inkSurface)
    {
        unloadInkSurface();
        uncreateInkSurface();
    }

    unloadPhotos();
}

void page_model::unload()
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return;
    }

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> UNloading page...      : genericIdentity() = %i : current level = %i : target level = self") %
        genericIdentity() %
        static_cast<b::uint32>(_currentLoadLevel)
        );

    if ((_lastRequestLoadLevel != page_level::page_self) && (_lastRequestLoadLevel != page_level::page_none))
    {
        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
            L">> Cannot unload a page that is still in use!"
            );

        return;
    }

    unloadFull();
    unloadThumbnail();

    _currentLoadLevel = page_level::page_self;
}

void page_model::onChildChanged(
    const b::uint32& typeToken,
    const b::uint32& identity
    )
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return;
    }

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_GENERIC_MODEL,
        boost::wformat(L"page_model::onChildChanged() : genericIdentity() = %i - Changed: type = %i - identity = %i change. ") %
        genericIdentity() %
        typeToken %
        identity
        );

    if (_linkedData == false)
    {
        _linkedData = true;
        _pageTable->updateLinkedData(genericIdentity(), _linkedData);
    }

    onChanged();
}

void page_model::onSelfChanged()
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_GENERIC_MODEL,
        boost::wformat(L"page_model::onSelfChanged() : genericIdentity() = %i. ") %
        genericIdentity()
        );

    onChanged();
}

void page_model::onPhotoMoved(
    const std::shared_ptr<iphoto_model>& photo,
    const b::float32& scale,
    const m::point2d& position,
    const b::float32& angle
    )
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return;
    }
}

void page_model::onInkSurfaceModelReady(
    const std::shared_ptr<iink_surface_model>& inkSurface)
{
    // this will be called before page_model is done initializing so we know it's not initialized yet.
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L"** page_model::onInkSurfaceModelReady() : id = %i : inkSurface = %i ...") %
        genericIdentity() %
        inkSurface->storageId()
        );

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> <<>> page_model::onInkSurfaceModelReady() : id = %i : firing onInkSurfaceModelReady(inkSurface = %i)") %
        genericIdentity() %
        inkSurface->storageId()
        );

    _observerSource.raise(
        [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
        {
            observer->onInkSurfaceModelReady(inkSurface);
        });
}

void page_model::onStrokeModelAttached(
    const std::shared_ptr<iink_stroke_model>& newStroke)
{
    if (!isAccessible())
    {
        return;
    }

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L"** page_model::onStrokeModelAttached() : id = %i : inkStrokeId = %i ...") %
        genericIdentity() %
        newStroke->storageId()
        );
}

void page_model::onStrokeModelReady(
    const std::shared_ptr<iink_stroke_model>& newStroke)
{
    if (!isAccessible())
    {
        return;
    }
}

void page_model::onStrokeModelRemoved(
    const std::shared_ptr<iink_stroke_model>& stroke)
{
    if (!isAccessible())
    {
        return;
    }
}

void page_model::onInkSurfaceChildrenReady(
        const std::shared_ptr<iink_surface_model>& inkSurface)
{
    if (!isAccessible())
    {
        return;
    }
}

void page_model::loadPhotos(const b::uint32& pass, bool& allDone)
{
    if (pass < _photosLoadPass)
    {
        allDone = false;
        return;
    }
    if (pass > _photosLoadPass)
    {
        allDone = true;
        return;
    }

    b::uint32 remainingCount = static_cast<b::uint32>(_photoIds.size());
    if (_photoIds.size() != _photos.size())
    {
        _photos.clear(); // Some, but not all, photo models have been initialized.  Clear the photo models and start again.
        HANDLE allDoneEvent = ::CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
        std::vector<std::shared_ptr<iphoto_model>> tempPhotos;
        for (auto photoId : _photoIds)
        {
            // re-create/observe
             std::shared_ptr<iphoto_model> photo = recreatePhoto(photoId);

            // attach in temp vector
            tempPhotos.push_back(photo);

            // load
            std::shared_ptr<iinternal_photo_model> internalPhotoModel = std::dynamic_pointer_cast<iinternal_photo_model>(photo);
            internalPhotoModel->loadAsync(
                    [allDoneEvent, &remainingCount]()
                    {
                        if (InterlockedDecrement(&remainingCount) == 0)
                        {
                            SetEvent(allDoneEvent);
                        }
                    }
                );
        }
        WaitForSingleObjectEx(allDoneEvent, INFINITE, FALSE);

        // attach
        for (auto &photo : tempPhotos)
        {
            this->attachPhotoModel(photo);
        }
    } 
    else 
    {
        for (auto &photo : _photos)
        {
            _observerSource.raise(
                [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
                {
                    observer->onPhotoModelAdded(photo);
                });
        }
    }

    allDone = true;
}

void page_model::unloadPhotos()
{
    for (auto &photo : _photos)
    {
        std::dynamic_pointer_cast<iinternal_photo_model>(photo)->unload();
        unobservePhoto(photo);
    }
    _photos.clear();
}

void page_model::unloadThumbnail()
{
    _thumbnail.empty();
}

void page_model::loadFull(b::uint32 pass, bool& done)
{
    bool inkSurfaceLoadDone = false;
    bool allPhotosLoadDone = false;

    if (pass == _inkSurfaceCreatePass)
    {
        // assume inkSurfaceId is present.
        // create object, and observe.
        recreateInkSurface();
    }
    if (pass >= _inkSurfaceCreatePass)
    {
        // assumes inkSurface object has been created, load it.
        loadInkSurface(pass, inkSurfaceLoadDone);
    }
    if (pass >= _photosCreatePass)
    {
        // assumes photoIds is present
        // for each id { create, observe, load, attach }
        loadPhotos(pass, allPhotosLoadDone);   // will fire load complete event.
    }
    if (inkSurfaceLoadDone && allPhotosLoadDone)
    {
        done = true;
    }
}

page_level page_model::loadLevel()
{
    return _currentLoadLevel;
}

void page_model::stopLoading()
{
    // TRACK: bug 463: GMileka: Need to set a flag so that loading stops as soon as possible.
}

void page_model::load(const b::uint32 pass, const page_level targetLevel)
{
    // we should not lock here since the events raised may cause a deadlock.
    if (_disposed == true)
    {
        return;
    }

    b::stopwatch ws;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> page_model::load()         : page id = %i, current level = %i, target level = %i, pass = %i : entering...") %
        genericIdentity() %
        static_cast<b::uint32>(_currentLoadLevel) %
        static_cast<b::uint32>(targetLevel) %
        pass
        );

    // this will be entered only once since data members never get unloaded.
    if (_initialized == false)
    {
        loadSelf();
    }

    if (targetLevel == page_level::page_self || targetLevel == page_level::page_none)
    {
        unloadFull();
        unloadThumbnail();
        _currentLoadLevel = page_level::page_self;

        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
            boost::wformat(L">> <<>> page_model::load() : id = %i : firing onPageLoadLevelSet(page = %i, level = %i)") %
            genericIdentity() %
            genericIdentity() %
            static_cast<b::uint32>(page_level::page_self)
            );

        _observerSource.raise(
            [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
            {
                observer->onPageLoadLevelSet(shared_from_this(), page_level::page_self);
            });
    }

    if (targetLevel == page_level::page_thumbnail)
    {
        loadThumbnail();
        unloadFull();
        _currentLoadLevel = page_level::page_self;

        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
            boost::wformat(L">> <<>> page_model::load() : id = %i : firing onPageLoadLevelSet(page = %i, level = %i)") %
            genericIdentity() %
            genericIdentity() %
            static_cast<b::uint32>(page_level::page_thumbnail)
            );

        // do not set the current load level since thumbnail is stateless.
        _observerSource.raise(
            [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
            {
                observer->onPageLoadLevelSet(shared_from_this(), page_level::page_thumbnail);
            });
    }

    if (targetLevel == page_level::page_full)
    {
        b::Boolean done;
        loadFull(pass, done);

        if (done == true)
        {
            if (_currentLoadLevel < page_level::page_full)
            {
                _currentLoadLevel = page_level::page_full;
            }

            DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
                boost::wformat(L">> <<>> page_model::load() : id = %i : firing onPageLoadLevelSet(page = %i, level = %i)") %
                genericIdentity() %
                genericIdentity() %
                static_cast<b::uint32>(page_level::page_full)
                );

            _observerSource.raise(
                [&](const std::shared_ptr<ipage_model_observer>& observer, void* context)
                {
                    observer->onPageLoadLevelSet(shared_from_this(), page_level::page_full);
                });
        }
    }

    _lastRequestLoadLevel = targetLevel;

    constants::performanceService()->collect(performance_event::modelLoad, ws.getElapsedMilliseconds()); 
}

void page_model::setLoadLevelAsync(const page_level targetLevel)
{
    // this can be the entry point to initialize page_model through loading.
    // so, we only check for '_disposed'.
    if (_disposed)
    {
        return;
    }

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> page_model::setLoadLevelAsync() : page id = %i : scheduled for loading at level %i...") %
        genericIdentity() %
        static_cast<b::uint32>(targetLevel)
        );

    std::shared_ptr<iinternal_notebook_model> notebook = std::dynamic_pointer_cast<iinternal_notebook_model>(_parent.lock());
    notebook->loadPageAsync(genericIdentity(), targetLevel);
}

void page_model::dispose()
{
    BAJA_LOCK(_mutex);
    if (!isAccessible())
    {
        return;
    }
    // dispose children.

    // dispose ink_surface
    std::shared_ptr<iinternal_ink_surface_model> inkSurfaceInternal = std::dynamic_pointer_cast<iinternal_ink_surface_model>(_inkSurface);
    if (inkSurfaceInternal)
    {
        inkSurfaceInternal->dispose();
    }
    else
    {
        _inkSurfaceFactory->removeFromDB(genericIdentity());
    }
    _inkSurface = nullptr;

    // dispose photos...
    if (_photos.size() != 0)
    {
        concurrency::concurrent_vector<std::shared_ptr<iphoto_model>>::iterator photoIt;
        for (photoIt = _photos.begin(); photoIt < _photos.end(); ++photoIt)
        {
            std::shared_ptr<iinternal_photo_model> photoInternal = std::dynamic_pointer_cast<iinternal_photo_model>(*photoIt);
            photoInternal->dispose();
        }
        _photos.clear();
    }
    else
    {
        _photoModelFactory->removeFromDB(genericIdentity());
    }

    removeFromDB();
    _initialized = false;
    _disposed = true;
}

#ifdef DEBUG_ENABLE_MODELS
void page_model::dump(bool includeChildren, std::wstring prefix)
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L"%s genericIdentity() = %i,\tPage Number = %i,\t_paperType = %i,\t_initialized = %s,\t_inDB = %s") %
        prefix.c_str() %
        genericIdentity() %
        this->pageNumber() %
        _pagePaperType %
        (_initialized ? L"true" : L"false") %
        (_inDB ? L"true" : L"false")
        );

    if (includeChildren == false)
    {
        return;
    }

    // dump children...
    if (_inkSurface != nullptr)
    {
        DEBUG_MODEL_DUMP(_inkSurface, includeChildren, prefix);
    }

    concurrency::concurrent_vector<std::shared_ptr<iphoto_model>>::iterator photoIt;
    for (photoIt = _photos.begin(); photoIt < _photos.end(); ++photoIt)
    {
        DEBUG_MODEL_DUMP((*photoIt), includeChildren, prefix);
    }
}
#endif

concurrency::concurrent_vector<std::shared_ptr<iphoto_model>>::iterator page_model::findPhoto(const id& id)
{
    auto it = std::find_if(
        _photos.begin(), 
        _photos.end(), 
        [&id](const std::shared_ptr<iphoto_model>& p) 
        { 
            return p->storageId() == id; 
        }
    );
    return it;
}

std::shared_ptr<ipage_model> journal::model::createPageModel(
    const b::uint32& typeToken,
    const std::shared_ptr<inotebook_model>& parent,
    const std::shared_ptr<journal::model::db::ipage_table>& pageTable,
    const b::uint32& pageId)
{
    std::shared_ptr<page_model> page = page_model::create(typeToken, parent, pageTable, pageId);
    return std::dynamic_pointer_cast<ipage_model>(page);
}